#!./bin/python
# ----------------------------------------------------------------------
# Install npkg packages
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------

# Python modules
import os
import tarfile
import tempfile
import json
import shutil
# NOC modules
from noc.core.fileutils import urlopen
from noc.config import config


class NPkg(object):
    VAR = config.path.npkg_root

    def __init__(self, config, verbose=False):
        self.config = config
        self.url = None
        self.required_versions = None
        self.installed_versions = None
        self.read_config()
        self.get_installed_versions()
        self.changed = False
        self.verbose = verbose

    def get_latest_version(self, r):
        url = self.url + r["name"] + ".latest"
        f = urlopen(url)
        data = f.read()
        if hasattr(data, "decode"):
            data = data.decode("utf-8")
        version = data.splitlines()[0]
        return version

    def read_config(self):
        with open(self.config) as f:
            cfg = json.load(f)
        self.url = cfg["url"]
        self.required_versions = {}
        for r in cfg["packages"]:
            if r["version"] == "latest":
                r["version"] = self.get_latest_version(r)
            self.required_versions[r["name"]] = r["version"]

    def get_installed_versions(self):
        if not os.path.isdir(self.VAR):
            os.makedirs(self.VAR)
        self.installed_versions = {}
        for pkg in os.listdir(self.VAR):
            if "@" not in pkg:
                continue
            if pkg.endswith(".json"):
                pkg = pkg[:-5]
                package, version = pkg.split("@")
                self.installed_versions[package] = version

    def get_manifest_path(self, pkg, version):
        return os.path.join(self.VAR, "%s@%s.json" % (pkg, version))

    def log(self, msg):
        if self.verbose:
            print(msg)

    def install(self, pkg):
        self.changed = True
        version = self.required_versions[pkg]
        url = self.url + pkg + "%40" + version + ".tar.bz2"
        with tempfile.NamedTemporaryFile() as tf:
            self.log("Downloading %s" % url)
            # Download
            f = urlopen(url)
            tf.write(f.read())
            tf.flush()
            f.close()
            # Process archive
            manifest = {
                "name": pkg,
                "version": version,
                "files": []
            }
            tar = tarfile.open(tf.name, "r:bz2")
            for mi in tar.getmembers():
                if mi.name.startswith("/") or ".." in mi.name:
                    continue  # Skip suspicious paths
                manifest["files"] += [mi.name]
                self.log("Extracting %s" % mi.name)
                tar.extract(mi)
            # Write manifest
            with open(self.get_manifest_path(pkg, version), "w") as f:
                json.dump(manifest, f)

    def uninstall(self, pkg):
        """
        Uninstall packages
        """
        self.changed = True
        mp = self.get_manifest_path(pkg, self.installed_versions[pkg])
        with open(mp) as f:
            manifest = json.load(f)
        for f in manifest["files"]:
            if os.path.isdir(f):
                shutil.rmtree(f)
            elif os.path.isfile(f):
                os.unlink(f)
        os.unlink(mp)

    def sync(self):
        """
        Sync all packages
        """
        for pkg in self.required_versions:
            if pkg in self.installed_versions:
                if self.installed_versions[pkg] != self.required_versions[pkg]:
                    self.log("Installed version mismatch: %s != %s. Uninstalling" % (self.installed_versions[pkg], self.required_versions[pkg]))
                    self.uninstall(pkg)
                else:
                    self.log("Already installed. Skipping.")
                    continue
            self.log("Installing %s (%s)" % (pkg, self.required_versions[pkg]))
            self.install(pkg)

        if self.changed:
            print("CHANGED")
        else:
            print("OK")


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("You have to set file to install to.")
        exit(1)
    if len(sys.argv) == 3:
        verbose = sys.argv[1] == "-v"
        config = sys.argv[2]
    else:
        verbose = False
        config = sys.argv[1]
    NPkg(config, verbose=verbose).sync()
